Extra: Venue 7 electrics#

If you see a straight line, it’s interpolated missing data. Try the sliders to see detail!

This plot is interesting because it shows a minimum of around 2 kW base load with lots of 2.5 kW spikes even when the building is unoccupied. If that electricity isn’t doing useful work, getting those under control could substantially cut the electricity bill. We aren’t sure yet what it is, but it looks a bit like once before in another building when we found thermostatically controlled electric heating left on.

import plotly.graph_objects as go
import numpy as np
import pandas as pd

df = pd.read_csv("venue-7-clampon-data.csv")
df["timestamp"] = pd.to_datetime(df['created_at'])
df = df.fillna(value=0)

phase1trace = go.Scatter(customdata=df, 
                    y=df['field1'], 
                    x = df['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 1',
                    )

phase2trace = go.Scatter(customdata=df, 
                    y=df['field2'], 
                    x = df['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 2',
                    )
phase3trace = go.Scatter(customdata=df, 
                    y=df['field3'], 
                    x = df['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 3',
                    )

g = go.FigureWidget(data=[phase1trace,phase2trace,phase3trace])
g.layout.title = 'CurrentCost clamp-on meter readings'
g.layout.xaxis.title= 'timestamp'
g.layout.yaxis.title = "Watts"
g.layout.width = 1000
g.layout.height = 500

fig = go.Figure(g)

fig.update_layout(
    hovermode='x unified',
    hoverlabel=dict(
        bgcolor="white",
        # font_size=16,
        font_family="Rockwell"
    )
)

# Add range slider
fig.update_layout(
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(
                     label="All",
                     step="all"
                     ),
                                dict(count=1,
                     label="Hour",
                     step="hour",
                     stepmode="todate"),
                dict(count=1,
                     label="Day",
                     step="day",
                     stepmode="backward"),
                dict(count=7,
                     label="Week",
                     step="day",
                     stepmode="backward"),
                dict(count=1,
                     label="Year",
                     step="year",
                     stepmode="backward")
            ])
        ),
        rangeslider=dict(
            visible=True,
        ),
        type="date"
    )
)


# fig.update_yaxes(range=[50, 60])  



# fig.update_yaxes(range = [-5, df['temperature'].max()+5])

fig.show()

# second figure 

sumtrace = go.Scatter(customdata=df, 
                    y=df['field1'] + df['field2'] + df['field3'], 
                    x = df['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='sum of three phases',
                    )

g2 = go.FigureWidget(data=[sumtrace])
g2.layout.title = 'CurrentCost clamp-on meter readings - simple sum of phases'
g2.layout.xaxis.title= 'timestamp'
g2.layout.yaxis.title = "Watts"
g2.layout.width = 1000
g2.layout.height = 500

# print(g2)
# graph_objects does data validation, and for some reason on github but not my machine
# the figure has invalid stuff somewhere in the dictionary. Try skipping it.
fig2 = go.Figure(g2, skip_invalid="True")

#print(fig2)

fig2.update_layout(
    hovermode='x unified',
    hoverlabel=dict(
        bgcolor="white",
        # font_size=16,
        font_family="Rockwell"
    )
)

# Add range slider
fig2.update_layout(
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(
                     label="All",
                     step="all"
                     ),
                                dict(count=1,
                     label="Hour",
                     step="hour",
                     stepmode="todate"),
                dict(count=1,
                     label="Day",
                     step="day",
                     stepmode="backward"),
                dict(count=7,
                     label="Week",
                     step="day",
                     stepmode="backward"),
                dict(count=1,
                     label="Year",
                     step="year",
                     stepmode="backward")
            ])
        ),
        rangeslider=dict(
            visible=True,
        ),
        type="date"
    )
)


fig2.show()
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[1], line 103
     93 # second figure 
     95 sumtrace = go.Scatter(customdata=df, 
     96                     y=df['field1'] + df['field2'] + df['field3'], 
     97                     x = df['timestamp'], 
   (...)
    100                     name='sum of three phases',
    101                     )
--> 103 g2 = go.FigureWidget(data=[sumtrace])
    104 g2.layout.title = 'CurrentCost clamp-on meter readings - simple sum of phases'
    105 g2.layout.xaxis.title= 'timestamp'

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/site-packages/plotly/graph_objs/_figurewidget.py:616, in FigureWidget.__init__(self, data, layout, frames, skip_invalid, **kwargs)
      5 def __init__(
      6     self, data=None, layout=None, frames=None, skip_invalid=False, **kwargs
      7 ):
      8     """
      9     Create a new :class:FigureWidget instance
     10 
   (...)
    614         is invalid AND skip_invalid is False
    615     """
--> 616     super(FigureWidget, self).__init__(data, layout, frames, skip_invalid, **kwargs)

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/site-packages/plotly/basewidget.py:117, in BaseFigureWidget.__init__(self, data, layout, frames, skip_invalid, **kwargs)
    108 def __init__(
    109     self, data=None, layout=None, frames=None, skip_invalid=False, **kwargs
    110 ):
   (...)
    115     # with the `layout` constructor parameter of the `widgets.DOMWidget`
    116     # ipywidgets class
--> 117     super(BaseFigureWidget, self).__init__(
    118         data=data,
    119         layout_plotly=layout,
    120         frames=frames,
    121         skip_invalid=skip_invalid,
    122         **kwargs,
    123     )
    125     # Validate Frames
    126     # ---------------
    127     # Frames are not supported by figure widget
    128     if self._frame_objs:

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/site-packages/plotly/basedatatypes.py:524, in BaseFigure.__init__(self, data, layout_plotly, frames, skip_invalid, **kwargs)
    519 self._data_objs = data
    521 # ### Import clone of trace properties ###
    522 # The _data property is a list of dicts containing the properties
    523 # explicitly set by the user for each trace.
--> 524 self._data = [deepcopy(trace._props) for trace in data]
    526 # ### Create data defaults ###
    527 # _data_defaults is a tuple of dicts, one for each trace. When
    528 # running in a widget context, these defaults are populated with
   (...)
    532 # Note: No property should exist in both the _data and
    533 # _data_defaults for the same trace.
    534 self._data_defaults = [{} for _ in data]

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/site-packages/plotly/basedatatypes.py:524, in <listcomp>(.0)
    519 self._data_objs = data
    521 # ### Import clone of trace properties ###
    522 # The _data property is a list of dicts containing the properties
    523 # explicitly set by the user for each trace.
--> 524 self._data = [deepcopy(trace._props) for trace in data]
    526 # ### Create data defaults ###
    527 # _data_defaults is a tuple of dicts, one for each trace. When
    528 # running in a widget context, these defaults are populated with
   (...)
    532 # Note: No property should exist in both the _data and
    533 # _data_defaults for the same trace.
    534 self._data_defaults = [{} for _ in data]

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/copy.py:146, in deepcopy(x, memo, _nil)
    144 copier = _deepcopy_dispatch.get(cls)
    145 if copier is not None:
--> 146     y = copier(x, memo)
    147 else:
    148     if issubclass(cls, type):

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
    228 memo[id(x)] = y
    229 for key, value in x.items():
--> 230     y[deepcopy(key, memo)] = deepcopy(value, memo)
    231 return y

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/copy.py:153, in deepcopy(x, memo, _nil)
    151 copier = getattr(x, "__deepcopy__", None)
    152 if copier is not None:
--> 153     y = copier(memo)
    154 else:
    155     reductor = dispatch_table.get(cls)

File /opt/hostedtoolcache/Python/3.8.14/x64/lib/python3.8/copy.py:159, in deepcopy(x, memo, _nil)
    157     rv = reductor(x)
    158 else:
--> 159     reductor = getattr(x, "__reduce_ex__", None)
    160     if reductor is not None:
    161         rv = reductor(4)

KeyboardInterrupt: 

We think this in watts, but the clamp-on meter is under-documented for three phase. If it’s not watts, the electricity use is a bit lower than what this says. We still need to do the calculation for kilowatt-hours to sanity check this against some hand meter readings, although we know the metering has been OK in the past (on a different meter).

The equipment we are using isn’t very common and we only have one set, but getting a smart meter is another way of seeing this kind of information. Admittedly, with half hourly readings a smart meter isn’t nearly as informative. This is roughly what the same data would look like with a smart meter:



# downsample to every 30 minutes

df.field1 = df.field1.astype(int)
df.field2 = df.field2.astype(int) 
df.field3 = df.field3.astype(int)

# aggregate over 30 minute intervals - this is the interval for smart meter readings.
s = df.resample('30T', on='timestamp', origin='start').agg({'field1':'mean','field2':'mean','field3':'mean'})
s['timestamp'] = s.index 
s = s.fillna(value=0)

downsampled1 = go.Scatter(customdata=s, 
                    y=s['field1']/1000, 
                    x = s['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 1',
                    )
downsampled2 = go.Scatter(customdata=s, 
                    y=s['field2']/1000, 
                    x = s['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 2',
                    )
downsampled3 = go.Scatter(customdata=s, 
                    y=s['field3']/1000, 
                    x = s['timestamp'], 
                    mode='lines', 
                    hoverinfo='all', 
                    name='phase 3',
                    )

g3 = go.FigureWidget(data=[downsampled1, downsampled2, downsampled3])
g3.layout.title = 'CurrentCost clamp-on meter readings - aggregated for half hour slots'
g3.layout.xaxis.title= 'timestamp'
g3.layout.yaxis.title = "kW"
g3.layout.width = 1000
g3.layout.height = 500

fig3 = go.Figure(g3)

fig3.update_layout(
    hovermode='x unified',
    hoverlabel=dict(
        bgcolor="white",
        # font_size=16,
        font_family="Rockwell"
    )
)

# Add range slider
fig3.update_layout(
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(
                     label="All",
                     step="all"
                     ),
                                dict(count=1,
                     label="Hour",
                     step="hour",
                     stepmode="todate"),
                dict(count=1,
                     label="Day",
                     step="day",
                     stepmode="backward"),
                dict(count=7,
                     label="Week",
                     step="day",
                     stepmode="backward"),
                dict(count=1,
                     label="Year",
                     step="year",
                     stepmode="backward")
            ])
        ),
        rangeslider=dict(
            visible=True,
        ),
        type="date"
    )
)


fig3.show()